﻿// Copyright 2008 Chris Varley (http://www.contractdriven.net/)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;

namespace ContractDriven
{
	/// <summary>
	/// An internally used thread-safe cache of delegates to call against objects to guarantee invariant contracts.
	/// </summary>
	internal class InvariantCache
	{

		/// <summary>
		/// An object to use to lock the cache as it is being updated.
		/// </summary>
		private static readonly object locker = new object();

		/// <summary>
		/// the cache itself.
		/// </summary>
		private readonly static Dictionary<MarshalByRefObject, Dictionary<Delegate, string>> objectAssertions = new Dictionary<MarshalByRefObject, Dictionary<Delegate, string>>(); 

		/// <summary>
		/// Add an explicitly stated delegate to the cache. 
		/// </summary>
		/// <param name="context">The object which owns this delegate</param>
		/// <param name="assertion">A Contract.AssertionStatementDelegate delegate that returns true or false.</param>
		/// <param name="description">An exception message to display if false.</param>
		internal static void Add(MarshalByRefObject context, Contract.AssertionStatementDelegate assertion, string description)
		{
			var assertions = GetAssertions(context);
			if (assertions.ContainsKey(assertion)) return;
						
			//do thread safe add
			lock (locker)
			{
				if (!assertions.ContainsKey(assertion))
				{
					assertions.Add(assertion, description);
				}
			}
		}

		/// <summary>
		/// Add an strongly typed assertion delegate to the cache. 
		/// </summary>
		/// <param name="context">The object which owns this delegate</param>
		/// <param name="assertion">A Contract.AssertionObjectDelegate delegate that returns true or false.</param>
		internal static void Add(MarshalByRefObject context, Contract.AssertionObjectDelegate assertion)
		{
			var assertions = GetAssertions(context);
			if (assertions.ContainsKey(assertion)) return;
			
			//do thread safe add
			lock (locker)
			{
				if (!assertions.ContainsKey(assertion))
				{
					assertions.Add(assertion, string.Empty);
				}
			}
		}

		/// <summary>
		/// <para>A list of the types that have delegates registered to them.</para>
		/// <para>Internally used by Contract.ShutdownCheck() to ensure that all types have been correctly disposed.</para>
		/// </summary>
		internal static string[] RegisteredTypes{
			get
			{
				{
					//copy to array for thread safety
					var keys = new MarshalByRefObject[objectAssertions.Keys.Count];
					objectAssertions.Keys.CopyTo(keys, 0);

					var typeNames = new List<string>();
					foreach (var o in keys)
					{
						if (!typeNames.Contains(o.GetType().ToString()))
						{
							typeNames.Add(o.GetType().ToString());
						}
					}
					return typeNames.ToArray();
				}
			}
		}

		/// <summary>
		/// Get a list of delegates to call for the specified object.
		/// </summary>
		/// <param name="context">The object for which to get the delagates</param>
		/// <returns>A Dictionary containing the delegates to run and failure messages for each delegate</returns>
		internal static Dictionary<Delegate, string> GetAssertions(MarshalByRefObject context)
		{
			if (!objectAssertions.ContainsKey(context))
			{
				// do thread safe add
				lock (locker)
				{
					if (!objectAssertions.ContainsKey(context))
					{
						objectAssertions.Add(context, new Dictionary<Delegate, string>());
					}
				}
			}

			return objectAssertions[context];
		}

		/// <summary>
		/// Remove any assertion delegates from the cache for the specified object
		/// </summary>
		/// <param name="context">the object for which to remove delegates.</param>
		internal static void Remove(MarshalByRefObject context)
		{
			//remove context object from cache.
			if (!objectAssertions.ContainsKey(context)) return;

			// do thread safe remove
			lock (locker)
			{
				if (objectAssertions.ContainsKey(context))
				{
					objectAssertions.Remove(context);
				}
			}

		}
	}
}
